<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>

  <body>
    <button class="btn">通知执行</button>

    <script>
      class Sub {
        constructor() {
          // 创建事件池（关键就是这个结构的设计）
          this.pond = {};
          // {
          //   'AAA': [fn1, fn2, ...],
          //   'BBB': [fn1, fn2, ...]
          // }
        }

        on(event, fn) {
          if (!this.pond[event]) this.pond[event] = [];
          if (!this.pond[event].includes(fn)) this.pond[event].push(fn);
        }
        off(event, fn) {
          if (!fn) {
            // 如果没有指定 fn，则直接把当前传进来的事件类型event 移除
            delete this.pond[event];
            return;
          }
          if (!this.pond[event]) return;
          this.pond[event] = this.pond[event].filter(item => item !== fn);
        }
        emit(event, ...params) {
          if (!this.pond[event]) return;
          this.pond[event].forEach(fn => fn(...params));
        }
      }

      let fn1 = (x, y) => console.log('fn1', x, y);
      let fn2 = () => {
        console.log('fn2');
        sub1.off('AAA', fn1);
        sub1.off('AAA', fn2);
      };
      let fn3 = () => console.log('fn3');
      let fn4 = () => console.log('fn4');
      let fn5 = (x, y) => console.log('fn5', x, y);

      let sub1 = new Sub();
      let sub2 = new Sub();

      sub1.on('AAA', fn1);
      sub1.on('AAA', fn2);
      sub1.on('AAA', fn3);
      sub1.on('AAA', fn4);
      sub1.on('BBB', fn5);

      sub2.on('CCC', fn5);

      // setTimeout(() => {
      //   sub1.emit('AAA', 10, 20);
      // })

      const btn = document.querySelector('.btn');
      btn.addEventListener('click', function () {
        sub1.emit('AAA', 10, 20);
        sub1.emit('BBB', 100, 200);
        sub2.emit('CCC', 1000, 2000);
      });
    </script>
  </body>
</html>
